package _.mail.util;

import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;

import org.budo.support.lang.util.StringUtil;
import org.budo.support.lang.util.SystemUtil;
import org.budo.support.slf4j.Slf4j;
import org.slf4j.Logger;

import _.javax.mail.MailSessionUtil;
import _.javax.mail.StoreUtil;
import _.javax.mail.TransportUtil;

import org.budo.mail.exception.AuthenticationFailedRuntimeException;

/**
 * @author li
 * @version 1 (2014年7月5日 下午4:24:01)
 * @since Java7
 * @see _.mail.javax.ReceiveMailUtil
 */
public class MailServerUtil {
    private static final Logger log = Slf4j.getLogger();

    public static boolean mailServerAvailable(String protocol, String host, String email, String password, boolean isSsl, Integer port) {
        if (StringUtil.equalsIgnoreCase("SMTP", protocol)) { // SMTP
            return smtpMailServerAvailable(host, email, password, isSsl, port);
        } else if (StringUtil.equalsIgnoreCase("POP3", protocol)) { // POP3
            return popMailServerAvailable(protocol, host, email, password, isSsl, port);
        } else if (StringUtil.equalsIgnoreCase("IMAP", protocol)) { // IMAP
            return imapMailServerAvailable(protocol, host, email, password, isSsl, port);
        } else {
            log.error("mailServerAvailable error, protocol=" + protocol);
            return false;
        }
    }

    public static Store doConnectStore(Session session, String protocol, String host, Integer port, String username, String password, Boolean ssl) {
        if ("pop3".equalsIgnoreCase(protocol)) { // POP 协议使用验证邮件渠道时候的方式连接
            Store store = null;
            try {
                store = StoreUtil.getStore(protocol, host, username, password, ssl, port);
                store = StoreUtil.connect(store, username, password);
                if (!store.isConnected()) {
                    throw new IllegalStateException("pop3 store connect fail, protocol=" + protocol + ", host=" + host + ", port=" + port + ", username=" + username + ", ssl=" + ssl);
                }
                return store;
            } catch (Exception e) {
                StoreUtil.closeStore(store);
                return null;
            }
        } else {
            return StoreUtil.connect(session, protocol, host, port, username, password);
        }
    }

    private static boolean imapMailServerAvailable(final String protocol, final String host, final String username, final String password, final boolean isSsl, final Integer port) {
        final long connectStoreTimeout = 10;

        ExecutorService executorService = null;
        FutureTask<Store> futureTask = null;
        try {
            executorService = Executors.newSingleThreadExecutor();

            futureTask = new FutureTask<Store>(new Callable<Store>() {
                public Store call() throws Exception {

                    Store connectdStore = null;
                    try {
                        log.info("start futureTask connectStoreTimeout=" + connectStoreTimeout + ", protocol=" + protocol + ", host=" + host + ", port=" + port + ", username=" + username + ", ssl=" + isSsl);
                        Session session = preapreSession(protocol, port, isSsl);
                        connectdStore = doConnectStore(session, protocol, host, port, username, password, isSsl);
                        return connectdStore;
                    } catch (Throwable e) {
                        if (StringUtil.contains(e + "", "java.net.SocketTimeoutException: Read timed out")) { // 超时
                            log.error("MailUtil doConnectStore error 3, connectdStore=" + connectdStore + " ,e=" + e);
                        } else {
                            log.error("MailUtil doConnectStore error 4, connectdStore=" + connectdStore + " ,e=" + e, e);
                        }

                        StoreUtil.closeStore(connectdStore);
                        return null;
                    }
                }
            });

            executorService.submit(futureTask);
            Store store = futureTask.get(connectStoreTimeout, TimeUnit.SECONDS);
            return null != store;
        } catch (TimeoutException e) {
            futureTask.cancel(true); // 停止任务
            executorService.shutdownNow(); // 停止线程
            log.error("connectStore timeout connectStoreTimeout=" + connectStoreTimeout + ", protocol=" + protocol + ", host=" + host + ", port=" + port + ", username=" + username + ", ssl=" + isSsl);
            return false;
        } catch (AuthenticationFailedRuntimeException e) {
            if (null != futureTask) {
                futureTask.cancel(true);
            }
            if (null != executorService) {
                executorService.shutdownNow();
            }
            return false;
        } catch (Throwable e) {
            if (null != futureTask) {
                futureTask.cancel(true);
            }
            if (null != executorService) {
                executorService.shutdownNow();
            }
            log.error("connectStore error connectStoreTimeout=" + connectStoreTimeout + ", protocol=" + protocol + ", host=" + host + ", port=" + port + ", username=" + username + ", ssl=" + isSsl, e);
            return false;
        } finally {
            if (null != executorService && !executorService.isShutdown()) {
                executorService.shutdown();
            }
        }
    }

    protected static Session preapreSession(String protocol, Integer port, boolean isSsl) {
        Properties properties = SystemUtil.getProperties();

        if (isSsl) {
            properties.setProperty("mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
            properties.setProperty("mail.pop3.socketFactory.fallback", "false");
            properties.setProperty("mail.imap.ssl.enable", "true"); // imap.ssl 支持
            properties.setProperty("mail.imap.ssl.trust", "*"); // imap.ssl 支持
        }

        properties.setProperty("mail.pop3.port", port + "");
        properties.setProperty("mail.pop3.socketFactory.port", port + "");
        properties.setProperty("mail.store.protocol", protocol);
        // properties.setProperty("mail.stmp.timeout", "" + (10 * 1000)); // mail.stmp.timeout int I/O连接超时时间，单位为毫秒，默认为永不超时
        properties.setProperty("mail.pop3.timeout", "15000");
        properties.setProperty("mail.pop3.connectiontimeout", "15000");
        properties.setProperty("mail.smtp.connectiontimeout", "15000");
        properties.setProperty("mail.smtp.timeout", "15000");
        properties.setProperty("mail.imap.connectiontimeout", "15000");
        properties.setProperty("mail.imap.timeout", "15000");

        return Session.getInstance(properties);
    }

    /**
     * 验证收件箱时链接邮件服务器的方式
     * 
     * @see _.mail.javax.JavaxMailReceiverImpl#doConnectStore(Session, String, String, Integer, String, String)
     */
    public static boolean popMailServerAvailable(String protocol, String host, String email, String password, boolean isSsl, Integer port) {
        boolean flag = false;
        Store store = null;
        try {
            store = StoreUtil.getStore(protocol, host, email, password, isSsl, port);
            store.connect(email, password);
            flag = store.isConnected();
        } catch (Exception e) {
            log.error("popMailServerAvailable error " + e, e);
        } finally {
            StoreUtil.closeStore(store);
        }
        return flag;
    }

    public static boolean smtpMailServerAvailable(String host, String email, String password, boolean isSsl, Integer port) {
        boolean isConnected = false;
        Session session = MailSessionUtil.getSmtpSession(host, email, password, isSsl, port);

        Transport transport = null;
        try {
            transport = session.getTransport();
            transport.connect(host, email, password);
            isConnected = transport.isConnected();
        } catch (NoSuchProviderException e) {
            log.error("smtpMailServerAvailable NoSuchProviderException " + e, e);
        } catch (MessagingException e) {
            log.error("smtpMailServerAvailable MessagingException " + e, e);
        } finally {
            TransportUtil.close(transport);
        }
        return isConnected;
    }
}